/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package lucava.importer.uspeed;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import lucava.importer.uspeed.matcher.AssociationMatcher;
import lucava.importer.uspeed.matcher.AttributeMatcher;
import lucava.importer.uspeed.matcher.InheritanceMatcher;
import lucava.importer.uspeed.matcher.MethodMatcher;
import lucava.importer.uspeed.matcher.NoteMatcher;
import lucava.importer.uspeed.matcher.ParameterMatcher;
import lucava.importer.uspeed.matcher.StereotypeMatcher;
import lucava.importer.uspeed.matcher.SyntaxPattern;
import lucava.metamodel.uspeed.AttributeElement;
import lucava.metamodel.uspeed.ClassElement;
import lucava.metamodel.uspeed.MethodElement;
import lucava.metamodel.uspeed.ParameterElement;
import lucava.metamodel.uspeed.RelationElement;
import lucava.metamodel.uspeed.RelationFactory;
import lucava.metamodel.uspeed.UspeedDocument;

/**
 * 
 * @author glenn
 */
public class FileImporter {

	private UspeedDocument uspeedDocument;
	private Boolean debug = false;

	/**
	 * parsing d'un fichier Uspeed
	 * 
	 * @param filename
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public UspeedDocument load(String filename) throws FileNotFoundException,
			IOException {
		return this.load(filename, false);
	}

	public UspeedDocument load(String filename, Boolean debug)
			throws FileNotFoundException, IOException {

		this.debug = debug;
		FileReader fr = new FileReader(filename);
		BufferedReader br = new BufferedReader(fr);

		return this.buildDocument(br);
	}

	/**
	 * parsing d'un contenu Uspeed
	 * 
	 * @param content
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public UspeedDocument loadContent(String content)
			throws FileNotFoundException, IOException {
		return this.loadContent(content, false);
	}

	public UspeedDocument loadContent(String content, Boolean debug)
			throws FileNotFoundException, IOException {
		this.debug = debug;
		StringReader sr = new StringReader(content);
		BufferedReader br = new BufferedReader(sr);

		return this.buildDocument(br);
	}

	/**
	 * Construction du Uspeed document
	 * 
	 * @param br
	 * @return
	 * @throws IOException
	 */
	private UspeedDocument buildDocument(BufferedReader br) throws IOException {
		uspeedDocument = new UspeedDocument();
		uspeedDocument.setName("default");

		// Parsing de chasue ligne du contenu
		String s;
		while ((s = br.readLine()) != null) {
			this.parseLine(s);
		}
		return uspeedDocument;
	}

	/**
	 * Parsing d'une ligne du contenu
	 * 
	 * @param s
	 */
	private void parseLine(String s) {

		Matcher matcherAssoc = SyntaxPattern.getAssocPattern().matcher(s);
		Matcher matcherInherit = SyntaxPattern.getInheritancePattern().matcher(
				s);
		Matcher matcherNote = SyntaxPattern.getNotePattern().matcher(s);
		Matcher matcherMeth = SyntaxPattern.getMethodPattern().matcher(s);
		Matcher matcherAttr = SyntaxPattern.getAttributePattern().matcher(s);
		Matcher matcherStereo = SyntaxPattern.getStereotypePattern().matcher(s);

		if (matcherAssoc.find()) {
			this.parseAssociation(matcherAssoc);
		} else if (matcherInherit.find()) {
			this.parseInheritance(matcherInherit);
		} else if (matcherNote.find()) {
			this.parseNote(matcherNote);
		} else if (matcherMeth.find()) {
			this.parseMethod(matcherMeth);
		} else if (matcherAttr.find()) {
			this.parseAttribute(matcherAttr);
		} else if (matcherStereo.find()) {
			this.parseStereotype(matcherStereo);
		} else {
			this.debug("syntax not found");
		}
	}

	/**
	 * Ajout d'un stéréotype
	 * 
	 * @param matcher
	 */
	private void parseStereotype(Matcher matcher) {

		StereotypeMatcher stereoMatcher = new StereotypeMatcher(matcher);
		debug(stereoMatcher.toString());

		ClassElement classElt = getClassByName(stereoMatcher.getClassName());
		classElt.setStereotype(stereoMatcher.getStereotype());

		if (null != stereoMatcher.getDescription()
				&& !stereoMatcher.getDescription().isEmpty()) {
			classElt.addComment(stereoMatcher.getStereotype()
					+ " stéreotype : " + stereoMatcher.getDescription());
		}

	}

	/**
	 * Construction d'une méthode
	 * 
	 * @param matcher
	 */
	private void parseMethod(Matcher matcher) {
		MethodMatcher methMatcher = new MethodMatcher(matcher);
		debug(methMatcher.toString());

		MethodElement method = new MethodElement();
		method.setName(methMatcher.getMethodName());
		method.setType(methMatcher.getMethodType());
		if (null != methMatcher.getDescription()) {
			method.addComment(methMatcher.getDescription());
		}

		if (null != methMatcher.getVisibility()
				&& !methMatcher.getVisibility().equals("@")) {
			method.setVisibility(methMatcher.getVisibility());
		}

		Matcher matcherArgs = SyntaxPattern.getArgumentsPattern().matcher(
				methMatcher.getParameters());

		while (matcherArgs.find()) {
			this.parseParameter(method, matcherArgs);
		}

		getClassByName(methMatcher.getClassName()).addMethod(method);
	}

	/**
	 * Construction d'un parametre de méthode
	 * 
	 * @param method
	 * @param matcher
	 */
	private void parseParameter(MethodElement method, MatchResult matcher) {

		ParameterMatcher paramMatcher = new ParameterMatcher(matcher);
		debug(paramMatcher.toString());

		ParameterElement param = new ParameterElement();
		param.setName(paramMatcher.getParameterName());
		param.setType(paramMatcher.getParameterType());

		method.addParameter(param);

	}

	/**
	 * Construction d'un attribut
	 * 
	 * @param matcher
	 */
	private void parseAttribute(MatchResult matcher) {

		AttributeMatcher attrMatcher = new AttributeMatcher(matcher);
		debug(attrMatcher.toString());

		AttributeElement attr = new AttributeElement();
		attr.setName(attrMatcher.getAttributeName());
		attr.setType(attrMatcher.getAttributeType());

		if (null != attrMatcher.getDescription()) {
			attr.addComment(attrMatcher.getDescription());
		}

		if (null != attrMatcher.getVisibility()
				&& !attrMatcher.getVisibility().equals("@")) {
			attr.setVisibility(attrMatcher.getVisibility());
		}

		getClassByName(attrMatcher.getClassName()).addAttribute(attr);

	}

	/**
	 * Construction d'un lien d'héritage
	 * 
	 * @param matcher
	 */
	private void parseInheritance(Matcher matcher) {

		InheritanceMatcher inheritMatcher = new InheritanceMatcher(matcher);
		debug(inheritMatcher.toString());

		ClassElement sourceClass = getClassByName(inheritMatcher
				.getSourceClassName());

		if (null != inheritMatcher.getAssociationName()
				&& inheritMatcher.getAssociationName().isEmpty()) {
			sourceClass.addComment(inheritMatcher.getTargetClassName()
					+ " Inheritance : " + inheritMatcher.getAssociationName());
		}

		if (inheritMatcher.getModifier().equals("^^")) {
			sourceClass.addExtendedClass(inheritMatcher.getTargetClassName());
		} else if (inheritMatcher.getModifier().equals("^-")) {
			sourceClass.addImplementedInterface(inheritMatcher
					.getTargetClassName());
		} else {
			debug("WARNING : Unknown Inheritance type");
		}

	}

	/**
	 * Construction d'une association
	 * 
	 * @param matcher
	 */
	private void parseAssociation(Matcher matcher) {

		AssociationMatcher assocMatcher = new AssociationMatcher(matcher);
		debug(assocMatcher.toString());

		RelationElement relation = RelationFactory.build(assocMatcher
				.getModifier());
		relation.setName(assocMatcher.getAssociationName());
		relation.setTargetClass(assocMatcher.getTargetClassName());
		relation.setSourceCard(assocMatcher.getSourceCard());
		relation.setTargetCard(assocMatcher.getTargetCard());
		relation.setDirectional(assocMatcher.getArrowEnd().equals("->"));

		getClassByName(assocMatcher.getSourceClassName()).addRelation(relation);

	}

	/**
	 * Construction d'un commentaire
	 * 
	 * @param matcher
	 */
	private void parseNote(Matcher matcher) {
		NoteMatcher noteMatcher = new NoteMatcher(matcher);
		debug(noteMatcher.toString());

		ClassElement classElt = getClassByName(noteMatcher.getClassName());
		if (null != noteMatcher.getNoteText()) {
			classElt.addComment(noteMatcher.getNoteText());
		}

	}

	/**
	 * Recherche d'une classe par son nom, Si elle n'existe pas elle est crée
	 * 
	 * @param sourceClass
	 * @return
	 */
	private ClassElement getClassByName(String sourceClass) {
		ClassElement result;
		if (uspeedDocument.hasClass(sourceClass)) {
			result = uspeedDocument.getClass(sourceClass);
		} else {
			result = new ClassElement();
			result.setName(sourceClass);
			uspeedDocument.addClass(sourceClass, result);
		}
		return result;

	}

	private void debug(String message) {
		if (debug) {
			System.out.println(message);
		}
	}
}
